<?php
/**
  * @shortdesc Class that generate a captcha-image with text and a form to fill in this text
  * @public
  * @author Horst Nogajski, (mail: coding@nogajski.de)
  * @version 1.5.0
  * @date 2007-December-24
  * 
  * @ link http://hn273.users.phpclasses.org/browse/package/1569.html
  *
  **/
class captcha
{

    ////////////////////////////////
    //
    //    PUBLIC PARAMS
    //

        /**
          * @shortdesc Absolute path to a Tempfolder (with trailing slash!). This must be writeable for PHP and also accessible via HTTP, because the image will be stored there.
          * @type string
          * @public
          *
          **/
        var $tempfolder;

        /**
          * @shortdesc Absolute path to folder with TrueTypeFonts (with trailing slash!). This must be readable by PHP.
          * @type string
          * @public
          *
          **/
        var $TTF_folder;

        /**
          * @shortdesc A List with available TrueTypeFonts for random char-creation. CASE-SENSITIVE!!
          * @type mixed[array|string]
          * @public
          *
          **/
        var $TTF_RANGE  = array();

        /**
          * @shortdesc How many chars the generated text should have
          * @type integer
          * @public
          *
          **/
        var $chars        = 6;

        /**
          * @shortdesc If TRUE, only chars from 0-9 and A-F are used as the Keycode, otherwise nearly 0-9 and A-Z is used. (have a look at function 'generate_private')
          * @type boolean
          * @public
          *
          **/
        var $use_only_md5 = FALSE;

        /**
          * @shortdesc The minimum size a Char should have
          * @type integer
          * @public
          *
          **/
        var $minsize    = 20;

        /**
          * @shortdesc The maximum size a Char can have
          * @type integer
          * @public
          *
          **/
        var $maxsize    = 40;

        /**
          * @shortdesc The maximum degrees a Char should be rotated. Set it to 30 means a random rotation between -30 and 30.
          * @type integer
          * @public
          *
          **/
        var $maxrotation = 25;

        /**
          * @shortdesc Background noise On/Off (if is Off, a grid will be created)
          * @type boolean
          * @public
          *
          **/
        var $noise        = TRUE;

        /**
          * @shortdesc This will only use the 216 websafe color pallette for the image.
          * @type boolean
          * @public
          *
          **/
        var $websafecolors = FALSE;

        /**
          * @shortdesc Switches language, available are 'en' and 'de'. You can easily add more. Look in CONSTRUCTOR.
          * @type string
          * @public
          *
          **/
        var $lang        = "en";

        /**
          * @shortdesc If a user has reached this number of try's without success, he will moved to the $badguys_url
          * @type integer
          * @public
          *
          **/
        var $maxtry        = 3;

        /**
          * @shortdesc Gives the user the possibility to generate a new captcha-image.
          * @type boolean
          * @public
          *
          **/
        var $refreshlink = TRUE;

        /**
          * @shortdesc If a user has reached his maximum try's, he will located to this url.
          * @type boolean
          * @public
          *
          **/
        var $badguys_url = "/";

        /**
          * Number between 1 and 32
          *
          * @shortdesc Defines the position of 'current try number' in (32-char-length)-string generated by function get_try()
          * @type integer
          * @public
          *
          **/
        var $secretposition = 21;

        /**
          * @shortdesc The string is used to generate the md5-key.
          * @type string
          * @public
          *
          **/
        var $secretstring = "This is a very secret string. Nobody should know it, =:)";

        /**
          * @shortdesc Outputs configuration values for testing
          * @type boolean
          * @public
          *
          **/
        var $debug = FALSE;

        /**
          * @shortdesc Is only needed when working with function "Display_Form_Part", could be 'POST' or 'GET'
          * @type string
          * @public
          *
          **/
        var $form_action_method = 'POST';


    ////////////////////////////////
    //
    //    PRIVATE PARAMS
    //

        /** @private **/
        var $lx;                   // width of picture
        /** @private **/
        var $ly;                   // height of picture
        /** @private **/
        var $jpegquality = 80;     // image quality
        /** @private **/
        var $noisefactor = 9;      // this will multiplyed with number of chars
        /** @private **/
        var $nb_noise;             // number of background-noise-characters
        /** @private **/
        var $TTF_file;             // holds the current selected TrueTypeFont
        /** @private **/
        var $msg1;
        /** @private **/
        var $msg2;
        /** @private **/
        var $buttontext;
        /** @private **/
        var $refreshbuttontext;
        /** @private **/
        var $public_K;
        /** @private **/
        var $private_K;
        /** @private **/
        var $key;                  // md5-key
        /** @private **/
        var $public_key;           // public key
        /** @private **/
        var $filename;             // filename of captcha picture
        /** @private **/
        var $gd_version;           // holds the Version Number of GD-Library
        /** @private **/
        var $QUERY_STRING;         // keeps the ($_GET) Querystring of the original Request
        /** @private **/
        var $current_try = 0;
        /** @private **/
        var $r;
        /** @private **/
        var $g;
        /** @private **/
        var $b;


    ////////////////////////////////
    //
    //    CONSTRUCTOR
    //

        /**
          * @shortdesc Extracts the config array and generate needed params.
          * @private
          * @type void
          * @return nothing
          *
          **/
        function captcha($config=false, $debug=FALSE, $secure=TRUE)
        {
        	if (!$config) {
        		$config = array(	      
		            // string: absolute path (with trailing slash!) to a php-writeable tempfolder which is also accessible via HTTP!
		            'tempfolder'     => engine.'/tmp/',
		
		            // string: absolute path (in filesystem, with trailing slash!) to folder which contain your TrueType-Fontfiles.
		            'TTF_folder'     => engine.'/fonts/',
		
		            // mixed (array or string): basename(s) of TrueType-Fontfiles, OR the string 'AUTO'. AUTO scanns the TTF_folder for files ending with '.ttf' and include them in an Array.
		            // Attention, the names have to be written casesensitive!
		            //'TTF_RANGE'    => 'NewRoman.ttf',
		            //'TTF_RANGE'    => 'AUTO',
		            //'TTF_RANGE'    => array('actionj.ttf','bboron.ttf','epilog.ttf','fresnel.ttf','lexo.ttf','tetanus.ttf','thisprty.ttf','tomnr.ttf'),
		            'TTF_RANGE'    => 'AUTO',
		
		            'chars'          => 5,       // integer: number of chars to use for ID
		            'minsize'        => 20,      // integer: minimal size of chars
		            'maxsize'        => 30,      // integer: maximal size of chars
		            'maxrotation'    => 25,      // integer: define the maximal angle for char-rotation, good results are between 0 and 30
		            'use_only_md5'   => FALSE,   // boolean: use chars from 0-9 and A-F, or 0-9 and A-Z
		
		            'noise'          => TRUE,    // boolean: TRUE = noisy chars | FALSE = grid
		            'websafecolors'  => FALSE,   // boolean
		            'refreshlink'    => TRUE,    // boolean
		            'lang'           => 'en',    // string:  ['en'|'de'|'fr'|'it'|'fi']
		            'maxtry'         => 9,       // integer: [1-9]
		
		            'badguys_url'    => '/',     // string: URL
		            'secretstring'   => 'A very, very secret string which is used to generate a md5-key!',
		            'secretposition' => 9        // integer: [1-32]
		    	);
        	}
            // Switch on/off Debugging
            $this->debug = ($debug===TRUE || $debug===FALSE) ? $debug : FALSE;

            // Test for GD-Library(-Version)
            $this->gd_version = $this->get_gd_version(TRUE);
            if($this->gd_version === 0) die("There is no GD-Library-Support enabled. The Captcha-Class cannot be used!");
            if($this->debug)
            {
                echo "\n<br>-Captcha-Debug: The available GD-Library has version ".$this->get_gd_version();
                // Additional Test if Freetype is enabled, too
                if(!function_exists('ImageTTFText'))
                {
                    echo "\n<br>-Captcha-Debug: Uuups! There is no FreeType-Support on this host!";
                    echo "\n<br>-Captcha-Debug: GD-Library AND Freetype-Support has to be enabled to write Characters into pictures!";
                    die("\n<br>EXIT");
                }
            }


            // extracts config array
            if(is_array($config))
            {
                if($secure && strcmp('4.2.0', phpversion()) < 0)
                {
                    if($this->debug) echo "\n<br>-Captcha-Debug: Extracts Config-Array in secure-mode!";
                    $valid = get_class_vars(get_class($this));
                    foreach($config as $k=>$v)
                    {
                        if(array_key_exists($k,$valid)) $this->$k = $v;
                    }
                }
                else
                {
                    if($this->debug) echo "\n<br>-Captcha-Debug: Extracts Config-Array in unsecure-mode!";
                    foreach($config as $k=>$v) $this->$k = $v;
                }
            }


            // set all messages
            // (if you add a new language, you also want to add a line to the function "notvalid_msg()" at the end of the class!)
            $usedchars = $this->use_only_md5 ? 'A..F' : 'A..Z';
            $this->usedchars = $usedchars;
            $this->messages = array(
                 'en'=>array(
                            'msg1'=>'Type the characters that you see in the box (<b>'.$this->chars.' characters</b>). The code can include characters <b>0..9</b> and <b>'.$usedchars.'</b>.',
                             'msg2'=>'I cannot read the characters. Generate a ',
                             'buttontext'=>'submit',
                             'refreshbuttontext'=>'new ID'
                            ),
                'de'=>array(
                            'msg1'=>'Bitte tragen Sie die <b>'.$this->chars.' Zeichen</b> in das Feld ein. Zeichen von <b>0..9</b> und <b>'.$usedchars.'</b> sind m�glich.',
                            'msg2'=>'Die Zeichen im Bild sind unleserlich. Generiere eine ',
                            'buttontext'=>'abschicken',
                            'refreshbuttontext'=>'neue ID'
                            ),
                'fr'=>array(
                            'msg1'=>'Vous devez lire et saisir les <b>'.$this->chars.' carat�res</b> pr�sent dans l\'image ci-dessus (<b>0..9</b> et <b>'.$usedchars.'</b>), dans le champ ci-dessous <br> et valider le formulaire.',
                            'msg2'=>'Les caract�res sont illisibles, merci de g�n�rer une nouvelle image.',
                            'buttontext'=>'valider',
                            'refreshbuttontext'=>'nouvelle ID'
                            ),
                'it'=>array(
                            'msg1'=>'Devi leggere e digitare i <b>'.$this->chars.' caratteri</b> tra <b>0..9</b> e <b>'.$usedchars.'</b>, e inviare il form.',
                            'msg2'=>'Oh no, non posso leggere questo. Genera un ',
                            'buttontext'=>'invia',
                            'refreshbuttontext'=>'nuovo ID'
                            ),
                'fi'=>array(
                            'msg1'=>'Kirjoita laatikossa lukeva varmistuskoodi (<b>'.$this->chars.' merkki�</b>). Koodi sis�lt�� merkkej� <b>0..9</b> ja <b>'.$usedchars.'</b>.',
                            'msg2'=>'En pysty lukemaan tuota. Generoi uusi ',
                            'buttontext'=>'L�het�',
                            'refreshbuttontext'=>'uusi ID'
                             ),
                'nl'=>array(
                            'msg1'=>'Geef de tekens in die u in het kader ziet (<b>'.$this->chars.' tekens</b>). De code kan de tekens <b>0..9</b> en <b>'.$usedchars.'</b> bevatten.',
                            'msg2'=>'Ik kan de tekens niet lezen. Genereer een ',
                            'buttontext'=>'verzenden',
                            'refreshbuttontext'=>'nieuw ID'
                            )
            );
            if(!isset($this->messages[$this->lang]) || !isset($this->messages[$this->lang]['msg1']) || !isset($this->messages[$this->lang]['msg2']) || !isset($this->messages[$this->lang]['buttontext']) || !isset($this->messages[$this->lang]['refreshbuttontext']))
            {
                $this->lang = 'en';
            }
            $this->msg1 = $this->sanitized_output($this->messages[$this->lang]['msg1']);
            $this->msg2 = $this->sanitized_output($this->messages[$this->lang]['msg2']);
            $this->buttontext = $this->sanitized_output($this->messages[$this->lang]['buttontext']);
            $this->refreshbuttontext = $this->sanitized_output($this->messages[$this->lang]['refreshbuttontext']);
            if($this->debug) echo "\n<br>-Captcha-Debug: Set messages to language: (".$this->lang.")";


            // Hackprevention
            if(
                (isset($_GET['maxtry']) || isset($_POST['maxtry']) || isset($_COOKIE['maxtry']))
                ||
                (isset($_GET['debug']) || isset($_POST['debug']) || isset($_COOKIE['debug']))
                )
            {
                $this->hack_prevention();
            }
            $_method = strtoupper($this->form_action_method)==='GET' ? '_GET' : '_POST';
            $is_refresh = isset($GLOBALS[$_method]['hncaptcha_refresh']) ? $GLOBALS[$_method]['hncaptcha_refresh'] : '';
            if($is_refresh !== '')
            {
                if($is_refresh !== $this->refreshbuttontext)
                {
                    $this->hack_prevention();
                }
                unset($GLOBALS[$_method]['hncaptcha_private_key']);
            }


            // check vars for maxtry, secretposition and min-max-size
            $this->maxtry = ($this->maxtry > 9 || $this->maxtry < 1) ? 3 : $this->maxtry;
            $this->secretposition = ($this->secretposition > 32 || $this->secretposition < 1) ? $this->maxtry : $this->secretposition;
            if($this->minsize > $this->maxsize)
            {
                $temp = $this->minsize;
                $this->minsize = $this->maxsize;
                $this->maxsize = $temp;
                if($this->debug) echo "<br>-Captcha-Debug: Oh dear! What do you think I mean with min and max? Switch minsize with maxsize.";
            }

            // sanitize pathes
            $this->tempfolder = str_replace(array('\\'), array('/'), $this->tempfolder);
            $this->tempfolder = (substr($this->tempfolder, -1) === '/') ? $this->tempfolder : $this->tempfolder . '/';
            if($this->debug) echo "\n<br>-Captcha-Debug: tempfolder is: (".$this->tempfolder.")";
            $this->TTF_folder = str_replace(array('\\'), array('/'), $this->TTF_folder);
            $this->TTF_folder = (substr($this->TTF_folder, -1) === '/') ? $this->TTF_folder : $this->TTF_folder . '/';
            if($this->debug)
            {
                if(is_readable($this->TTF_folder))
                {
                    echo "\n<br>-Captcha-Debug: TTF_folder is: (".$this->TTF_folder.")";
                }
                else
                {
                    echo "\n<br>-Captcha-Debug: TTF_folder is not readable! -(".$this->TTF_folder.")";
                }
            }
            if(!is_readable($this->TTF_folder)) die('Truetype-Directory is not readable!');


            // check TrueTypeFonts
            if(is_array($this->TTF_RANGE))
            {
                $TTF_TEMP = array();
                if($this->debug) echo "\n<br>-Captcha-Debug: Check given TrueType-Array! (".count($this->TTF_RANGE).")";
                for($T=0;$T<count($this->TTF_RANGE);$T++)
                {
                    if(is_readable($this->TTF_folder.$this->TTF_RANGE[$T]))
                    {
                        if($this->debug) echo "\n<br>-Captcha-Debug: add TrueTypeFile: (".$this->TTF_folder.$this->TTF_RANGE[$T].")";
                        $TTF_TEMP[] = $this->TTF_RANGE[$T];
                    }
                    else
                    {
                        if($this->debug) echo "\n<br>-Captcha-Debug: not found: TrueTypeFile-(".$this->TTF_folder.$this->TTF_RANGE[$T].")";
                    }
                }
                $this->TTF_RANGE = $TTF_TEMP;
                unset($TTF_TEMP);
                if($this->debug) echo "\n<br>-Captcha-Debug: Valid TrueType-files: (".count($this->TTF_RANGE).")";
                if(count($this->TTF_RANGE) < 1) die('No Truetypefont available for the CaptchaClass.');
            }
            else
            {
                if(strtoupper($this->TTF_RANGE)==='AUTO')
                {
                    $this->TTF_RANGE = array();
                    if($this->debug) echo "\n<br>-Captcha-Debug: Scans fontsdirectory for fontfiles!";
                    // Scan fontsdir for ttf-files
                    if($fonts_dir = opendir($this->TTF_folder))
                    {
                        while($file = @readdir($fonts_dir))
                        {
                            if(substr(strtolower($file), -4) != '.ttf')
                            {
                                continue;
                            }
                            if(is_readable($this->TTF_folder.$file))
                            {
                                if($this->debug) echo "\n<br>-Captcha-Debug: add TrueTypeFile: (".$this->TTF_folder.$file.")";
                                $this->TTF_RANGE[] = $file;
                            }
                        }
                        closedir($fonts_dir);
                    }
                    if($this->debug) echo "\n<br>-Captcha-Debug: Valid TrueType-files: (".count($this->TTF_RANGE).")";
                    if(count($this->TTF_RANGE) < 1) die('No Truetypefont available for the CaptchaClass.');
                }
                else
                {
                    if($this->debug) echo "\n<br>-Captcha-Debug: Check given TrueType-File! (".$this->TTF_RANGE.")";
                    if(!is_readable($this->TTF_folder.$this->TTF_RANGE)) die('No Truetypefont available for the CaptchaClass.');
                }
            }

            // select first TrueTypeFont
            $this->change_TTF();
            if($this->debug) echo "\n<br>-Captcha-Debug: Set current TrueType-File: (".$this->TTF_file.")";


            // get number of noise-chars for background if is enabled
            $this->nb_noise = $this->noise ? ($this->chars * $this->noisefactor) : 0;
            if($this->debug) echo "\n<br>-Captcha-Debug: Set number of noise characters to: (".$this->nb_noise.")";


            // set dimension of image
            $this->lx = ($this->chars + 1) * (int)(($this->maxsize + $this->minsize) / 1.5);
            $this->ly = (int)(2.4 * $this->maxsize);
            if($this->debug) echo "\n<br>-Captcha-Debug: Set image dimension to: (".$this->lx." x ".$this->ly.")";


            // keep params from original GET-request
            if($this->form_action_method !== 'GET')
            {
                $this->QUERY_STRING = strlen(trim($_SERVER['QUERY_STRING'])) > 0 ? '?'.strip_tags($_SERVER['QUERY_STRING']) : '';
                $refresh = $_SERVER['PHP_SELF'].$this->QUERY_STRING;
                if($this->debug) echo "\n<br>-Captcha-Debug: Keep this params from original GET-request: (".$this->QUERY_STRING.")";
            }


            // check Form_Vars
            $pub  = $this->get_form_var('hncaptcha_public_key');
            $priv = $this->get_form_var('hncaptcha_private_key');
            $try  = $this->get_form_var('hncaptcha');

            if($pub!==NULL)  $this->public_K = substr($pub,0,$this->chars);
            if($priv!==NULL) $this->private_K = substr($priv,0,$this->chars);
            $this->current_try = ($try===NULL) ? 0 : $this->get_try();

            if(!isset($GLOBALS[$_method]['hncaptcha_refresh'])) $this->current_try++;
            if($this->debug) echo "\n<br>-Captcha-Debug: Check {$this->form_action_method}-Vars, current try is: (".$this->current_try.")";


            // generate Keys
            $this->key = md5($this->secretstring);
            $this->public_key = substr(md5(uniqid(rand(),true)), 0, $this->chars);
            if($this->debug) echo "\n<br>-Captcha-Debug: Generate Keys, public key is: (".$this->public_key.")";

        }

    ////////////////////////////////
    //
    //    Функции для удобства
    //

        function show() {        	
        	echo '<input type="hidden" name="hncaptcha" value="'.$this->get_try(FALSE).'">'.$this->display_captcha();
        }
        
        /**
         * Проверить капчу
         *
         * @param string $name имя формы с капчей
         * @return bool
         */
        function validate($name='captcha') {
        	$this->private_K=$_REQUEST[$name];
        	return ($this->validate_submit()==1);
        }
        
        
    ////////////////////////////////
    //
    //    PUBLIC METHODS
    //
        
        /**
          *
          * @shortdesc displays a complete form with captcha-picture
          * @public
          * @type void
          * @return HTML-Output
          *
          **/
        function display_form($only_body=FALSE)
        {
            $try = $this->get_try(FALSE);
            if($this->debug) echo "\n<br>-Captcha-Debug: Generate a string which contains current try: ($try)";
            $s = '';
            if(!$only_body)
            {
                $s .= '<div id="captcha">';
                $s .= '<form class="captcha" name="captcha1" action="'.$_SERVER['PHP_SELF'].$this->QUERY_STRING.'" method="POST">'."\n";
            }
            $s .= '<input type="hidden" name="hncaptcha" value="'.$try.'">'."\n";
            $s .= '<p class="captcha_notvalid">'.$this->notvalid_msg().'</p>';
            $s .= '<p class="captcha_1">'.$this->display_captcha()."</p>\n";
            $s .= '<p class="captcha_1">'.$this->msg1.'</p>';
            $s .= '<p class="captcha_1"><input class="captcha" type="text" name="hncaptcha_private_key" value="" maxlength="'.$this->chars.'" size="'.$this->chars.'">&nbsp;&nbsp;';
            if($this->refreshlink)
            {
                $s .= '<p class="captcha_2">'.$this->msg2;
                $s .= ' <input class="captcha_2" type="submit" name="hncaptcha_refresh" value="'.$this->refreshbuttontext.'">'."</p>\n";
            }
            $s .= '<input class="captcha" type="submit" value="'.$this->buttontext.'">'."</p>\n";
            if(!$only_body)
            {
                $s .= '</form>'."\n";
                $s .= '</div>';
            }
            if($this->debug) echo "\n<br>-Captcha-Debug: Output Form with captcha-image.<br><br>";
            return $s;
        }


        /**
          *
          * @shortdesc displays a form-part with captcha-picture
          * @public
          * @type void
          * @return HTML-Output
          *
          **/
        function display_form_part($which='all')
        {
            $ret = '';
            $which = strtolower($which);

            if($which==='all')
            {
                $ret .= $this->display_form(TRUE);
                if($this->debug) echo "\n<br>-Captcha-Debug: Output Form-Part with captcha-image.<br><br>";
                return $ret;
            }

            if($which==='image')
            {
                $try = $this->get_try(FALSE);
                if($this->debug) echo "\n<br>-Captcha-Debug: Generate a string which contains current try: ($try)";
                $ret .= '<input type="hidden" name="hncaptcha" value="'.$try.'">'."\n";
                $ret .= $this->display_captcha()."\n";
            }

            if($which==='input')
            {
                $ret .= '<input class="captcha" type="text" name="hncaptcha_private_key" value="" maxlength="'.$this->chars.'" size="'.$this->chars.'">'."\n";
            }

            if($which==='text')
            {
                $ret .= $this->msg1."\n";
            }

            if($which==='text_notvalid')
            {
                $ret .= $this->notvalid_msg()."\n";
            }


            if($which==='refresh_text' || $which==='refreshtext')
            {
                $ret .= $this->msg2."\n";
            }

            if($which==='refresh_button' || $which==='refreshbutton')
            {
                $this->refreshlink = TRUE;
                $ret .= '<input class="captcha" type="submit" name="hncaptcha_refresh" value="'.$this->refreshbuttontext.'">'."\n";
            }


            if($this->debug) echo "\n<br>-Captcha-Debug: Output Form-Part: $which";
            return $ret;
        }


        /**
          *
          * @shortdesc validates POST-vars and return result
          * @public
          * @type integer
          * @return 0 = first call | 1 = valid submit | 2 = not valid | 3 = not valid and has reached maximum try's
          *
          **/
        function validate_submit()
        {
            if($this->check_captcha($this->public_K,$this->private_K))
            {
                if($this->debug) echo "\n<br>-Captcha-Debug: Validating submitted form returns: (1)";
                return 1;
            }
            else
            {
                if($this->current_try > $this->maxtry)
                {
                    if($this->debug) echo "\n<br>-Captcha-Debug: Validating submitted form returns: (3)";
                    return 3;
                }
                elseif($this->current_try > 0)
                {
                    if($this->debug) echo "\n<br>-Captcha-Debug: Validating submitted form returns: (2)";
                    return 2;
                }
                else
                {
                    if($this->debug) echo "\n<br>-Captcha-Debug: Validating submitted form returns: (0)";
                    return 0;
                }
            }
        }



    ////////////////////////////////
    //
    //    PRIVATE METHODS
    //

        /** @private **/
        function hack_prevention()
        {
            if($this->debug)
            {
                echo "\n<br>-Captcha-Debug: Buuh. You are a bad guy!<br><br>In production mode you would be redirected to this URL now: <a href=\"{$this->badguys_url}\">{$this->badguys_url}</a>!";
                exit(0);
            }
            else
            {
                if(isset($this->badguys_url) && !headers_sent())
                {
                    header('location: '.$this->badguys_url);
                }
            }
            die();
        }

        /** @private **/
        function display_captcha($onlyTheImage=FALSE)
        {
            $this->make_captcha();
            $is = getimagesize($this->get_filename());
            $ret = "\n".'<img class="captchapict" src="'.$this->get_filename_url().'" '.$is[3].' alt="This is a captcha-picture. It is used to prevent mass-access by robots. (see: www.captcha.net)" title="">'."\n";
            return $onlyTheImage ? $ret : $this->public_key_input().$ret;
        }

        /** @private **/
        function public_key_input()
        {
            return '<input type="hidden" name="hncaptcha_public_key" value="'.$this->public_key.'">';
        }

        /** @private **/
        function make_captcha()
        {
            $private_key = $this->generate_private();
            if($this->debug) echo "\n<br>-Captcha-Debug: Generate private key: ($private_key)";

            // create Image and set the apropriate function depending on GD-Version & websafecolor-value
            if($this->gd_version >= 2 && !$this->websafecolors)
            {
                $func1 = 'imagecreatetruecolor';
                $func2 = 'imagecolorallocate';
            }
            else
            {
                $func1 = 'imageCreate';
                $func2 = 'imagecolorclosest';
            }
            $image = $func1($this->lx,$this->ly);
            if($this->debug) echo "\n<br>-Captcha-Debug: Generate ImageStream with: ($func1())";
            if($this->debug) echo "\n<br>-Captcha-Debug: For colordefinitions we use: ($func2())";


            // Set Backgroundcolor
            $this->random_color(224, 255);
            $back =  @imagecolorallocate($image, $this->r, $this->g, $this->b);
            @ImageFilledRectangle($image,0,0,$this->lx,$this->ly,$back);
            if($this->debug) echo "\n<br>-Captcha-Debug: We allocate one color for Background: (".$this->r."-".$this->g."-".$this->b.")";

            // allocates the 216 websafe color palette to the image
            if($this->gd_version < 2 || $this->websafecolors) $this->makeWebsafeColors($image);


            // fill with noise or grid
            if($this->nb_noise > 0)
            {
                // random characters in background with random position, angle, color
                if($this->debug) echo "\n<br>-Captcha-Debug: Fill background with noise: (".$this->nb_noise.")";
                for($i=0; $i < $this->nb_noise; $i++)
                {
                    srand((double)microtime()*1000000);
                    $size    = intval(rand((int)($this->minsize / 2.3), (int)($this->maxsize / 1.7)));
                    srand((double)microtime()*1000000);
                    $angle    = intval(rand(0, 360));
                    srand((double)microtime()*1000000);
                    $x        = intval(rand(0, $this->lx));
                    srand((double)microtime()*1000000);
                    $y        = intval(rand(0, (int)($this->ly - ($size / 5))));
                    $this->random_color(160, 224);
                    $color    = $func2($image, $this->r, $this->g, $this->b);
                    srand((double)microtime()*1000000);
                    $text    = chr(intval(rand(45,250)));
                    @ImageTTFText($image, $size, $angle, $x, $y, $color, $this->change_TTF(), $text);
                }
            }
            else
            {
                // generate grid
                if($this->debug) echo "\n<br>-Captcha-Debug: Fill background with x-gridlines: (".(int)($this->lx / (int)($this->minsize / 1.5)).")";
                for($i=0; $i < $this->lx; $i += (int)($this->minsize / 1.5))
                {
                    $this->random_color(160, 224);
                    $color    = $func2($image, $this->r, $this->g, $this->b);
                    @imageline($image, $i, 0, $i, $this->ly, $color);
                }
                if($this->debug) echo "\n<br>-Captcha-Debug: Fill background with y-gridlines: (".(int)($this->ly / (int)(($this->minsize / 1.8))).")";
                for($i=0 ; $i < $this->ly; $i += (int)($this->minsize / 1.8))
                {
                    $this->random_color(160, 224);
                    $color    = $func2($image, $this->r, $this->g, $this->b);
                    @imageline($image, 0, $i, $this->lx, $i, $color);
                }
            }

            // generate Text
            if($this->debug) echo "\n<br>-Captcha-Debug: Fill forground with chars and shadows: (".$this->chars.")";
            for($i=0, $x = intval(rand($this->minsize,$this->maxsize)); $i < $this->chars; $i++)
            {
                $text    = strtoupper(substr($private_key, $i, 1));
                srand((double)microtime()*1000000);
                $angle    = intval(rand(($this->maxrotation * -1), $this->maxrotation));
                srand((double)microtime()*1000000);
                $size    = intval(rand($this->minsize, $this->maxsize));
                srand((double)microtime()*1000000);
                $y        = intval(rand((int)($size * 1.5), (int)($this->ly - ($size / 7))));
                $this->random_color(0, 127);
                $color    =  $func2($image, $this->r, $this->g, $this->b);
                $this->random_color(0, 127);
                $shadow = $func2($image, $this->r + 127, $this->g + 127, $this->b + 127);
                @ImageTTFText($image, $size, $angle, $x + (int)($size / 15), $y, $shadow, $this->change_TTF(), $text);
                @ImageTTFText($image, $size, $angle, $x, $y - (int)($size / 15), $color, $this->TTF_file, $text);
                $x += (int)($size + ($this->minsize / 5));
            }
            @ImageJPEG($image, $this->get_filename(), $this->jpegquality);
            $res = file_exists($this->get_filename());
            if($this->debug) echo "\n<br>-Captcha-Debug: Save Image with quality [".$this->jpegquality."] as (".$this->get_filename().") returns: (".($res ? 'TRUE' : 'FALSE').")";
            @ImageDestroy($image);
            if($this->debug) echo "\n<br>-Captcha-Debug: Destroy Imagestream.";
            if(!$res) die('Unable to save captcha-image.');
        }

        /** @private **/
        function makeWebsafeColors(&$image)
        {
            //$a = array();
            for($r = 0; $r <= 255; $r += 51)
            {
                for($g = 0; $g <= 255; $g += 51)
                {
                    for($b = 0; $b <= 255; $b += 51)
                    {
                        $color = imagecolorallocate($image, $r, $g, $b);
                        //$a[$color] = array('r'=>$r,'g'=>$g,'b'=>$b);
                    }
                }
            }
            if($this->debug) echo "\n<br>-Captcha-Debug: Allocate 216 websafe colors to image: (".imagecolorstotal($image).")";
            //return $a;
        }

        /** @private **/
        function random_color($min,$max)
        {
            srand((double)microtime() * 1000000);
            $this->r = intval(rand($min,$max));
            srand((double)microtime() * 1000000);
            $this->g = intval(rand($min,$max));
            srand((double)microtime() * 1000000);
            $this->b = intval(rand($min,$max));
            //echo " (".$this->r."-".$this->g."-".$this->b.") ";
        }

        /** @private **/
        function change_TTF()
        {
            if(is_array($this->TTF_RANGE))
            {
                srand((float)microtime() * 10000000);
                $key = array_rand($this->TTF_RANGE);
                $this->TTF_file = $this->TTF_folder.$this->TTF_RANGE[$key];
            }
            else
            {
                $this->TTF_file = $this->TTF_folder.$this->TTF_RANGE;
            }
            return $this->TTF_file;
        }

        /** @private **/
        function check_captcha($public,$private)
        {
            $res = 'FALSE';
            // when check, destroy picture on disk
            if(file_exists($this->get_filename($public)))
            {
                $res = @unlink($this->get_filename($public)) ? 'TRUE' : 'FALSE';
                if($this->debug) echo "\n<br>-Captcha-Debug: Delete image (".$this->get_filename($public).") returns: ($res)";
                $res = (strtolower($private)===strtolower($this->generate_private($public))) ? 'TRUE' : 'FALSE';
                if($this->debug) echo "\n<br>-Captcha-Debug: Comparing public with private key returns: ($res)";
            }
            return $res==='TRUE' ? TRUE : FALSE;
        }
            /* OLD FUNCTION, without HotFix from Daniel Jagszent :
                function check_captcha($public,$private)
                {
                    // when check, destroy picture on disk
                    if(file_exists($this->get_filename($public)))
                    {
                        $res = @unlink($this->get_filename($public)) ? 'TRUE' : 'FALSE';
                        if($this->debug) echo "\n<br>-Captcha-Debug: Delete image (".$this->get_filename($public).") returns: ($res)";
                    }
                    $res = (strtolower($private)==strtolower($this->generate_private($public))) ? 'TRUE' : 'FALSE';
                    if($this->debug) echo "\n<br>-Captcha-Debug: Comparing public with private key returns: ($res)";
                    return $res == 'TRUE' ? TRUE : FALSE;
                }
            */

        /** @private **/
        function get_filename($public='')
        {
            if($public==='') $public = $this->public_key;
            return $this->tempfolder.$public.'.jpg';
        }

        /** @private **/
        function get_filename_url($public='')
        {
            if($public==='') $public = $this->public_key;
            return str_replace($_SERVER['DOCUMENT_ROOT'], '', $this->tempfolder).$public.'.jpg';
        }

        /** @private **/
        function get_form_var($varname)
        {
            if($this->form_action_method==='POST')
            {
                if(isset($_POST[$varname]))
                {
                    return strip_tags($_POST[$varname]);
                }
            }
            if($this->form_action_method==='GET')
            {
                if(isset($_GET[$varname]))
                {
                    return strip_tags($_GET[$varname]);
                }
            }
            return NULL;
        }

        /** @private **/
        function get_try($in=TRUE)
        {
            $s = array();
            for($i = 1; $i <= $this->maxtry; $i++) $s[$i] = $i;

            if($in)
            {
                return (int)substr($this->get_form_var('hncaptcha'), (int)($this->secretposition - 1), 1);
            }
            else
            {
                $a = '';
                $b = '';
                for($i = 1; $i < $this->secretposition; $i++)
                {
                    srand((double)microtime()*1000000);
                    $a .= $s[intval(rand(1,$this->maxtry))];
                }
                for($i = 0; $i < (32 - $this->secretposition); $i++)
                {
                    srand((double)microtime()*1000000);
                    $b .= $s[intval(rand(1,$this->maxtry))];
                }
                return $a.$this->current_try.$b;
            }
        }

        /** @private **/
         function get_gd_version($major=NULL)
         {
            if(extension_loaded('gd') && function_exists('gd_info'))
            {
                $stats = gd_info();
				// changed: 27.01.2008 horst
				// to match e.g. "bundled (2.0.28 compatible)" and also "2.0 or higher"
                //if(preg_match("/\(([\d\.]+)/", $stats['GD Version'], $matches))
                if(preg_match("/([\d\.]+)/", $stats['GD Version'], $matches))
                {
                    $gd_version_number = $matches[1];
                }
            }
            else
            {
                $gd_version_number = '0';
            }
            return $major!==NULL ? (int)substr($gd_version_number,0,1) : (string)$gd_version_number;
        }

        /** @private **/
        function generate_private($public='')
        {
            if($public==='') $public = $this->public_key;
            if($this->use_only_md5)
            {
                $key = substr(md5($this->key.$public), 16 - (int)($this->chars / 2), $this->chars);
            }
            else
            {
                $key = substr(base64_encode(md5($this->key.$public)), 16 - (int)($this->chars / 2), $this->chars);
                $key = strtr($key, '0OoIi1B8+-_/=', 'WXxLL7452369H');
            }
            return $key;
        }

        /** @private **/
        function sanitized_output($txt)
        {
            $trans = get_html_translation_table(HTML_ENTITIES);
            $txt = strtr($txt, $trans);
            return str_replace(array('&lt;','&gt;','&amp;nbsp;'), array('<','>','&nbsp;'), $txt);
        }

        /**
          *
          * @shortdesc returns a message if the form validation has failed
          * @private
          * @type string
          * @return string message or blankline as placeholder
          *
          **/
        function notvalid_msg()
        {
            // blank line for all languages
            if($this->current_try == 1) return '&nbsp;<br>&nbsp;';

            // invalid try's: de
            if($this->lang == "de" && $this->current_try > 2 && $this->refreshlink) return $this->sanitized_output('Die Eingabe war nicht korrekt.<br>Tipp: Wenn Du die Zeichen nicht erkennen kannst, generiere neue mit dem Link unten!');
            if($this->lang == "de" && $this->current_try >= 2) return $this->sanitized_output('Die Eingabe war nicht korrekt. Bitte noch einmal versuchen:<br>&nbsp;');

            // invalid try's: fr
            if($this->lang == "fr" && $this->current_try > 2 && $this->refreshlink) return $this->sanitized_output('Saisie non valide. Veuillez essayer � nouveau:<br>Astuce: Si vous ne parvenez pas � lire les caract�res, vous pouvez g�n�rer une nouvelle image!');
            if($this->lang == "fr" && $this->current_try >= 2) return $this->sanitized_output('Saisie non valide. Veuillez essayer � nouveau:<br>&nbsp;');

            // invalid try's: fi
            if($this->lang == "fi" && $this->current_try > 2 && $this->refreshlink) return $this->sanitized_output('Ep�kelpo sy�te. Yrit� uudestaan:<br>Vihje: Jos et saa merkeist� selv��, generoi uusi koodi!');
            if($this->lang == "fi" && $this->current_try >= 2) return $this->sanitized_output('Ep�kelpo sy�te. Yrit� uudestaan:<br>&nbsp;');

            // invalid try's: nl
            if($this->lang == "nl" && $this->current_try > 2 && $this->refreshlink) return $this->sanitized_output('De ingevoerde code was onjuist. Probeer aub opnieuw:<br>Tip: Wanneer u de tekens niet kan lezen, kan u een nieuwe afbeelding genereren!');
            if($this->lang == "nl" && $this->current_try >= 2) return $this->sanitized_output('De ingevoerde code was onjuist. Probeer aub opnieuw:<br>&nbsp;');


            // THIS MUST BE THE LAST ENTRY IN FUNCTION, PLEASE ADD NEW LANGUAGES ABOVE THAT LINE!
            // invalid try's: en, AND THE DEFAULT, IF NO PART FOR A LANGUAGE IS DEFINED HERE, (BUT IN CONSTRUCTOR):
            if($this->current_try > 2 && $this->refreshlink) return $this->sanitized_output('No valid entry. Please try again:<br>Tip: If you cannot identify the chars, you can generate a new image!');
            if($this->current_try >= 2) return $this->sanitized_output('No valid entry. Please try again:<br>&nbsp;');
        }


} // END CLASS hn_CAPTCHA

?>